package de.hdu.mfz.ctrl;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import de.hdu.mfz.dao.MitfahrtDAO;
import de.hdu.mfz.dao.RouteDAO;
import de.hdu.mfz.dao.UserDAO;
import de.hdu.mfz.dto.AJAXMessage;
import de.hdu.mfz.dto.RouteZeile;
import de.hdu.mfz.dto.RouteZeile_Ort;
import de.hdu.mfz.model.Mitfahrt;
import de.hdu.mfz.model.Route;
import de.hdu.mfz.model.Routenpunkt;
import de.hdu.mfz.model.User;

@Controller
public class RouteController {
	RouteDAO routeDAO;
	UserDAO userDAO;
	MitfahrtDAO mitfahrtDAO;
	LoginController loginCtrl; //im LoginController befindet sich die Hilfsfunktion zum prüfen der HTTPSession
	    
	public void setRouteDAO (RouteDAO r) {
		this.routeDAO = r;	
	}
	public void setUserDAO (UserDAO u) {
		this.userDAO = u;
	}
	public void setMitfahrtDAO(MitfahrtDAO m){
		this.mitfahrtDAO = m;
	}
	public void setLoginCtrl(LoginController l) {
		this.loginCtrl = l;
	}
	
	/** Suche nach Fahrten (nur noch nicht beendetet)
	 * 
	 * @param von Abfahrtsort (String)
	 * @param nach Ankunftsort (String)
	 * @param tag Abfahrtsdaum und Uhrzeit (tt.mm.jjjj hh:mm)
	 * @return Objekt vom Typ AJAXMessages. AJAXMessages.returnMessage enthält eine Array von RouteZeile Objekten
	 */
	@RequestMapping(value="/sucheRoute.json", method=RequestMethod.POST)
	public @ResponseBody AJAXMessage searchRoute(String von, String nach, String tag, HttpSession session) {
		Date abfahrt;
		
		if (von == "") {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie Ihren Abfahrtsort an.");	
		}
		if (nach == "") {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie Ihren Zielort an.");	
		}
		if (tag == "") {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte gegen Sie ein Abfahrtsdatum an.");	
		}
		
		try {
			abfahrt = this.checkAndConvertDate(tag,null);
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie ein korrektes Datum ein! (tt.mm.jjjj)");
		}

		//ArrayList für Ergebnis. ArrayList hat den Vorteil, dass nach FIFO sortiert wird.
		ArrayList<RouteZeile> retListe = new ArrayList<RouteZeile>();

		List<Route> routen = routeDAO.findRoute(von, nach, abfahrt);
		try {
			//Für jede Route ein RouteZeile-Objekt erzeugen und in Ergebnis-Array speichern.
			for (Route r : routen)
				retListe.add(new RouteZeile(r, von, nach, this.mitfahrtDAO.findByRoute(r).size()));
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.ErrorMessage, "Internen Fehler");
		}
		
		return new AJAXMessage(true, AJAXMessage.FailureCode.None, retListe.toArray());
	}

	/** Liest eine Route anhand der ID und gibt ein RouteZeile Objekt zurück
	 * 
	 * @param id Eindeutige ID der Route (aus der Datenbank)
	 * @param session (HTTPSession, Wird von Swing übergeben) 
	 * @return Objekt vom Typ AJAXMessages. AJAXMessages.returnMessage enthält ein RouteZeile Objekt
	 */
	@RequestMapping(value="/getRoute.json", method=RequestMethod.POST)
	public @ResponseBody AJAXMessage getRoute(int id, HttpSession session) {
		try {
			Route r = routeDAO.findById(id);
			if (r == null)
				throw new Exception("Nicht gefunden");
			
			return new AJAXMessage(true, AJAXMessage.FailureCode.None, new RouteZeile(r, "", ""));
		} catch (Exception e) { 
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Fahrt wurde leider zwischenzeitlich gelöscht.");
		}
	}

	/** Eine Fahrt in der Datenbank speichern. Prüfung der Eingabefelder wird durchgeführt.
	 * Pflichtfelder:
	 * <ul>
	 * 	<li>abdatum</li>
	 * 	<li>abzeit</li>
	 * 	<li>andatum</li>
	 * 	<li>anzeit</li>
	 * 	<li>von</li>
	 * 	<li>nach</li>
	 * 	<li>plaetze</li>
	 * </ul>
	 * Zwischenhalte beginnen mit "ueber_". Nach dem Unterstrich muss eine Nummer folgen.
	 * 
	 * @param params Liste (Map<String, String>) der Eingabefelder
	 * @param session (HTTPSession, Wird von Swing übergeben) 
	 * @return Objekt vom Typ AJAXMessages. AJAXMessages.returnMessage enthält eine Nachricht an den Anwender (ggf. Fehlermeldung)
	 */
	@RequestMapping(value="/saveRoute.json", method=RequestMethod.POST)
	public @ResponseBody AJAXMessage saveRoute(@RequestParam Map<String, String> params, HttpSession session) {
		//Für diese Funktion ist ein Login erforderlich
		
		User myUser;
		try {
			myUser = loginCtrl.getUserFromSession(session);
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.LoginRequired, e.getMessage());
		}	
		
		if (myUser.isAdmin()) {
			//Admin-User kann keine Route erstellen, weil Admin nur Admin ist!
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Als Admin können sie keine Fahrt eintragen.");
		}
		
		
		//  1. Formularfelder in lokale Variablen speichern 
		String par_abdatum;
		String par_abzeit;
		String par_andatum;
		String par_anzeit;
		String par_von;
		String par_nach;
		String par_plaetze;
		try {
			par_abdatum = params.get("abdatum");
			par_abzeit = params.get("abzeit");
			par_andatum = params.get("andatum");
			par_anzeit = params.get("anzeit");
			par_von = params.get("von");
			par_nach = params.get("nach");
			par_plaetze = params.get("plaetze");
		} catch (Exception e) {
			//Fehler ausgeben, wenn übergebenes Formular unvollständig ist
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Schwerwiegender Fehler bei der Verarbeitung Ihrer Anfrage.");
		}		

		//  2. 'Dynamische' Liste mit Zwischenhalten abrufen und sortieren
		ArrayList<RouteZeile_Ort> orteListe = new ArrayList<RouteZeile_Ort>();
		try {
			//Am Anfang Abfahrtsort hinzufügen. Id ist immer 0
			orteListe.add(new RouteZeile_Ort(0, par_von));
			int tmpid; //tmpid ist zum Zwischenspeichern der Nummer des Zwischenhalts. Ginge auch ohne tmpid. Ist aber so leichter zum debuggen
			for (Map.Entry<String,String> einParameter : params.entrySet()) {
				if (einParameter.getKey().length() >= 7
					&& einParameter.getKey().substring(0,6).equals("ueber_") //Alle Felder mit den Zwischenorten beginnen mit ueber_
					&& ! einParameter.getKey().equals("ueber_blanko")) { 
					if (!einParameter.getValue().equals("")) {
						tmpid = Integer.parseInt(einParameter.getKey().substring(6));
						orteListe.add(new RouteZeile_Ort(tmpid, einParameter.getValue()));
					}
				}
			}
			java.util.Collections.sort(orteListe);
			//Zum Schluss Zielort hinzufügen. Id ist einfach um 1 höher als die höchste ID der bestehenden Zwischenhalte
			orteListe.add(new RouteZeile_Ort(orteListe.get(orteListe.size()-1).id+1, par_nach));
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Schwerwiegender Fehler bei der Verarbeitung Ihrer Anfrage.");
		}	
		
		//  3. Eingebewerte umwandeln und prüfen (Datum, Zahl, ...)
		Date abfahrt;
		Date ankunft;
		int iPlaetze;
		
		try {
			abfahrt = this.checkAndConvertDate(par_abdatum , par_abzeit);		
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie ein korrektes Abfahrtsdatum/-uhrzeit ein! (tt.mm.jjjj hh:mm)");
		}
		try {
			ankunft = this.checkAndConvertDate(par_andatum, par_anzeit);		
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie ein korrektes Ankunftsdatum/-uhrzeit ein! (tt.mm.jjjj hh:mm)");
		}
		
		if (abfahrt.compareTo(ankunft) > 0)
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Ankunft kann nicht vor Abfahrt liegen.");
		
		if (par_von.equals("")) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie den Abfahrtsort ein!");
		}
		if (par_nach.equals("")) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie den Zielort ein!");
		}
		
		try {
			iPlaetze = Integer.parseInt(par_plaetze);
			if (iPlaetze < 1 || iPlaetze > 99) {
				throw new Exception("ungültige Plätzeanzahl");
			}
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Bitte geben Sie die Anzahl Ihrer freien Plätze ein! (1-99)");
		}
			
			
		/* For Debugging....
		String a = "";
		for (RouteZeile_Ort o : orteListe) {
			a = a + o.id + ": " + o.name;
		}
		return new AJAXMessage(false, AJAXMessage.FailureCode.InfoMessage, a);*/
		
		//Route speichern. ---------------------------------------------------------------
		
		try {
			Route r = new Route(abfahrt, ankunft, iPlaetze, myUser);
			for (RouteZeile_Ort o : orteListe) { //In der ArrayList <orteListe> sind Start+Zwischenhalte+Ziel (sortiert) gespeichert.
				r.getListe().add(new Routenpunkt(o.name, o.id, r)); //Aus dem dto "Ort" ein Model-Objekt RoutenPunkt erzeugen
			}
			routeDAO.insert(r);
			
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Schwerwiegender Fehler beim Speichern der neuen Fahrt.");
		}	
		
		return new AJAXMessage(true, AJAXMessage.FailureCode.InfoMessage, "Ihre Fahrt wurde gespeichert.");		
	}
	
	/** Aktuell angemeldeten User als Mitfahrer in eine Fahrt eintragen.
	 * 
	 * @param id Eindeutige ID der Route
	 * @param session (HTTPSession, Wird von Swing übergeben) 
	 * @return Objekt vom Typ AJAXMessages. AJAXMessages.returnMessage enthält eine Nachricht an den Anwender.
	 */
	@RequestMapping(value="/joinRoute.json", method=RequestMethod.POST)
	public @ResponseBody AJAXMessage joinRoute(int id, String von, String nach, HttpSession session) {

		//Für diese Funktion ist ein Login erforderlich
		User myUser;
		try {
			myUser = loginCtrl.getUserFromSession(session);
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.LoginRequired, e.getMessage());
		}

		if (myUser.isAdmin()) {
			//Admin-User kann nirgends mitfahre, weil Admin nur Admin ist!
			return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Als Admin können sie keiner Route beitreten.");
		}
		
		try {
			Route r = this.routeDAO.findById(id);
			
			List<Mitfahrt> mitfahren = mitfahrtDAO.findByRoute(r);
			
			if (mitfahren.size() >= r.getPlaetze())
				return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Es sind leider keine Plätze mehr frei.");

			if (r.getFahrer().getUserId() == myUser.getUserId())
				return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Sie sind der Fahrer dieser Route und können nicht selber mitfahren.");
			
			for (Mitfahrt m : mitfahren) {
				if (m.getMitfahrer().getUserId() == myUser.getUserId())
					return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Sie sind bereits als Mitfahrer eingetragen.");
			}
			
			von = von.toUpperCase();
			nach = nach.toUpperCase();
			Routenpunkt rpvon=null;
			Routenpunkt rpnach=null;
			
			for(Routenpunkt rp: r.getListe()){ //Routepunkte zu "von" und "nach" finden. Funktioniert wie RouteDAO.findRoute
				if(rp.getOrt().toUpperCase().equals(von) && rpvon == null){
					rpvon = rp;
				}
				if((rp.getOrt().toUpperCase().equals(nach)) && 
						(rpvon != null)){
					rpnach = rp;
					break;
				}	
			}
			
			if (rpvon == null || rpnach == null)
				return new AJAXMessage(false, AJAXMessage.FailureCode.WarningMessage, "Route wurde zwischenzeitliche geändert");
			
			this.mitfahrtDAO.insert(new Mitfahrt(myUser, r, rpvon, rpnach));
			
			return new AJAXMessage(true, AJAXMessage.FailureCode.InfoMessage, "Gratulation! Sie sind dabei.");
		} catch (Exception e) {
			return new AJAXMessage(false, AJAXMessage.FailureCode.ErrorMessage, e.getMessage());
		}

		
	}
	
	
	

	/**Hilfsfunktion zum Umwandeln eines Strings in ein Date-Objekt
	 * Gültige Datumsformate:
	 * strDatum       strZeit
	 * ------------   -------
	 * - tt.MM.jjjj   hh:mm    
	 * - tt.MM.jj 	  hh:mm
	 * - tt.MM.jjjj
	 * - tt.MM.jj 
	 * Das kleinste, akzeptierte Datum ist der 01.01.2000
	 * 
	 * @param Datum als String
	 * @return Date-Objekt 
	 * @throws Exception: Falls Datum im falschen Format oder ungültig
	 */
	private Date checkAndConvertDate(String strDatum, String strZeit) throws Exception {
		Date tmpDate;
		GregorianCalendar cal = new GregorianCalendar();
		//HowTo String -> Date: http://stackoverflow.com/questions/999172/how-to-parse-date-in-java
		if (strZeit != null && strZeit.length() > 0) 
			tmpDate = new SimpleDateFormat("dd.MM.yyyy HH:mm").parse(strDatum + " " + strZeit);  //HH bedeutet 24 Stunden hh sind pm,am
		else {
			tmpDate = new SimpleDateFormat("dd.MM.yyyy").parse(strDatum);
		}
		
		/* 1. String in Datum umwandeln. 
		 * 2. Datum prüfen. Dazu braucht man in Java ein Calendar Objekt. Anders kann man mit einem Datum nicht rechnen
		 * 3. Datum prüfen auf zweistellig (wenn ja, dann + 2000 Jahre)
		 * 4. Datum zwischen 100 und 2000 wird nicht akzeptiert. Ist sicher ein Tippfehler*/
		
		cal.setTime(tmpDate);
		
		//Falls jemand 12 anstatt von 2012 eintippt, dann muss man 2000 Jahre addieren.
		if (cal.get(Calendar.YEAR) > 0 && cal.get(Calendar.YEAR) < 100)
			cal.add(Calendar.YEAR, 2000);
		if (cal.get(Calendar.YEAR) > 100 && cal.get(Calendar.YEAR) < 2000)
			throw new Exception("Datum ungültig");
		
		return cal.getTime();
	}
	
}
